package cn.jtool.dig.server.service.impl;

import cn.jtool.dig.common.model.Session;
import cn.jtool.dig.common.netty.CtxUtils;
import cn.jtool.dig.common.netty.SessionManager;
import cn.jtool.dig.server.comm.ServerManager;
import cn.jtool.dig.server.dao.entity.License;
import cn.jtool.dig.server.dao.entity.PortMapping;
import cn.jtool.dig.server.dao.repository.LicenseRepository;
import cn.jtool.dig.server.dao.repository.PortMappingRepository;
import cn.jtool.dig.server.domain.dto.ConnectionInfoDto;
import cn.jtool.dig.server.domain.reqeust.PortMappingParams;
import cn.jtool.dig.server.domain.reqeust.PortMappingQueryRequest;
import cn.jtool.dig.server.domain.response.PortMappingDetailResponse;
import cn.jtool.dig.server.domain.response.PortMappingListResposne;
import cn.jtool.dig.server.service.PortMappingService;
import cn.jtool.knife.core.result.PageResultEntity;
import cn.jtool.knife.core.util.AssertUtil;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import io.netty.channel.ChannelHandlerContext;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.domain.Example;
import org.springframework.stereotype.Service;

import java.net.InetSocketAddress;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Service
public class PortMappingServiceImpl implements PortMappingService {

    @Resource
    private PortMappingRepository portMappingRepository;

    @Resource
    private LicenseRepository licenseRepository;

    @Lazy
    @Resource
    private ServerManager serverManager;

    @Override
    public void create(PortMappingParams params) {
        PortMapping checkName = this.portMappingRepository.findOne(
                Example.of(PortMapping.builder().portMappingName(params.getPortMappingName()).build())
        ).orElse(null);
        AssertUtil.checkNotNull(checkName, "隧道名称已存在");
        PortMapping checkSource = this.portMappingRepository.findOne(
                Example.of(PortMapping.builder().sourceIp(params.getSourceIp()).sourcePort(params.getSourcePort()).build())
        ).orElse(null);
        AssertUtil.checkNotNull(checkSource, "源ip和源端口已存在");
        License license = this.licenseRepository.findOne(
                Example.of(License.builder().licenseKey(params.getLicenseKey()).build())
        ).orElse(null);
        AssertUtil.checkNull(license, "授权码不存在");

        PortMapping portMapping = PortMapping.builder()
                .portMappingName(params.getPortMappingName())
                .sourceIp(params.getSourceIp())
                .sourcePort(params.getSourcePort())
                .licenseId(license.getId())
                .licenseKey(params.getLicenseKey())
                .targetIp(params.getTargetIp())
                .targetPort(params.getTargetPort())
                .enableState(params.getEnableState())
                .whiteIps(params.getWhiteIps())
                .blackIps(params.getBlackIps())
                .notes(params.getNotes())
                .updateTime(new Date()).createTime(new Date()).build();
        this.portMappingRepository.save(portMapping);
        this.serverManager.startTunnel(this.selectOne(portMapping.getId()));
    }

    @Override
    public void delete(Integer id) {
        PortMapping portMapping = this.portMappingRepository.findById(id).orElse(null);
        if(Objects.nonNull(portMapping)){
            this.portMappingRepository.delete(portMapping);
        }
    }

    @Override
    public void delete(Iterable<? extends Integer> ids) {
        this.portMappingRepository.deleteAllById(ids);
    }

    @Override
    public void delete(Integer integer, Boolean isForce) {

    }

    @Override
    public PageResultEntity<PortMappingListResposne> listByPage(PortMappingQueryRequest params, int page, int limit) {
        PageHelper.startPage(page, limit);
        List<PortMapping> portMappings = this.portMappingRepository.findAll();
        List<Session> sessions = SessionManager.getSession(Session.SessionType.REAL);
        Map<Integer, List<Session>> sessionMap = sessions.stream().collect(Collectors.groupingBy(Session::getPortMappingId));
        List<PortMappingListResposne> portMappingListResposneList = portMappings.stream().map(tunnel -> {
            List<Session> sessionList = sessionMap.get(tunnel.getId());
            return PortMappingListResposne.builder()
                    .id(tunnel.getId())
                    .portMappingName(tunnel.getPortMappingName())
                    .sourceIp(tunnel.getSourceIp())
                    .sourcePort(tunnel.getSourcePort())
                    .licenseKey(tunnel.getLicenseKey())
                    .connectNumber(CollectionUtils.isNotEmpty(sessionList) ? sessionList.size() : 0)
                    .targetIp(tunnel.getTargetIp())
                    .targetPort(tunnel.getTargetPort())
                    .enableState(tunnel.getEnableState())
                    .createTime(tunnel.getCreateTime()).build();
        }).toList();
        PageInfo<PortMappingListResposne> pageInfo = new PageInfo<>(portMappingListResposneList);
        return PageResultEntity.of(pageInfo);
    }

    @Override
    public PortMappingDetailResponse selectOne(Integer id) {
        PortMapping portMapping = this.portMappingRepository.findById(id).orElse(null);
        AssertUtil.checkNull(portMapping, "隧道不存在");
        return PortMappingDetailResponse.builder()
                .id(portMapping.getId())
                .portMappingName(portMapping.getPortMappingName())
                .sourceIp(portMapping.getSourceIp())
                .sourcePort(portMapping.getSourcePort())
                .licenseKey(portMapping.getLicenseKey())
                .targetIp(portMapping.getTargetIp())
                .targetPort(portMapping.getTargetPort())
                .enableState(portMapping.getEnableState())
                .whiteIps(portMapping.getWhiteIps())
                .blackIps(portMapping.getBlackIps())
                .notes(portMapping.getNotes())
                .createTime(portMapping.getCreateTime())
                .updateTime(portMapping.getUpdateTime()).build();
    }

    @Override
    public PortMappingDetailResponse update(Integer id, PortMappingParams params) {
        PortMapping portMapping = this.portMappingRepository.findById(id).orElse(null);
        AssertUtil.checkNull(portMapping, "隧道不存在");

        License license = this.licenseRepository.findOne(Example.of(License.builder().licenseKey(params.getLicenseKey()).build())).orElse(null);
        AssertUtil.checkNull(license, "授权码不存在");

        BeanUtils.copyProperties(params, portMapping);
        portMapping.setLicenseId(license.getId());
        portMapping.setUpdateTime(new Date());
        this.portMappingRepository.saveAndFlush(portMapping);
        PortMappingDetailResponse portMappingDetailResponse = this.selectOne(id);
        this.serverManager.stopTunnel(portMappingDetailResponse);
        this.serverManager.startTunnel(portMappingDetailResponse);
        return portMappingDetailResponse;
    }


    public PortMapping selectBySourcePort(int port){
        return this.portMappingRepository.findOne(Example.of(PortMapping.builder().sourcePort(port).enableState(1).build())).orElse(null);
    }

    @Override
    public PageResultEntity<ConnectionInfoDto> connectList(int id) {
        List<Session> sessions = SessionManager.getSession(Session.SessionType.REAL);
        if(CollectionUtils.isNotEmpty(sessions)){
            List<ConnectionInfoDto> connectionInfoDtos = sessions.stream().filter(r -> Objects.equals(r.getPortMappingId(), id)).toList().stream().map(session -> {
                ChannelHandlerContext channelHandlerContext = session.getChannelHandlerContext();
                InetSocketAddress remoteInetSocketAddress = CtxUtils.getRemoteInetSocketAddress(channelHandlerContext);
                return ConnectionInfoDto.builder()
                        .id(session.getId())
                        .sourceIp(remoteInetSocketAddress.getHostName())
                        .sourcePort(remoteInetSocketAddress.getPort())
                        .connectTime(session.getCreateTime())
                        .recByteNumber(session.getReceiveBytes())
                        .sendByteNumber(session.getSendBytes())
                        .build();
            }).toList();
            return PageResultEntity.<ConnectionInfoDto>builder().items(connectionInfoDtos).total(connectionInfoDtos.size()).page(1).pages(1).limit(Integer.MAX_VALUE).build();
        }
        return new PageResultEntity<>();
    }

    @Override
    public void kill(int id) {
        Session session = SessionManager.getSession(id);
        if(Objects.nonNull(session)){
            session.disconnect();
        }
    }

    @Override
    public void ban(int tunnelId, int id) {
        PortMapping portMapping = this.portMappingRepository.findById(tunnelId).orElse(null);
        if(Objects.nonNull(portMapping)){
            Session session = SessionManager.getSession(id);
            portMapping.addBlackIps(CtxUtils.getRemoteInetSocketAddress(session.getChannelHandlerContext()).getHostName());
            portMapping.setUpdateTime(new Date());
            this.portMappingRepository.saveAndFlush(portMapping);
            session.disconnect();
        }
    }
}
